/**
 * Copyright © 2012, 2013 dr. ir. Jeroen M. Valk
 * 
 * This file is part of ComPosiX.
 * 
 * ComPosiX is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * ComPosiX is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * ComPosiX. If not, see <http://www.gnu.org/licenses/>.
 */

var assertionError = new Error("assertion error");
var log = log4javascript.getRootLogger();
var appender = new log4javascript.PopUpAppender();
var layout = new log4javascript.PatternLayout("[%-5p] %c - %m");
appender.setLayout(layout);
log.addAppender(appender);

function getline(fd, continuation) {
	cpx.getline(fd, continuation);
}

function putline(fd, line) {
	cpx.putline(fd, line);
}

function flush(fd) {
	cpx.flush(fd);
}

function assert(condition) {
	if (!condition) {
		throw assertionError;
	}
}

function Packet() {
	this.continuation = null;
	this.message = null;
}

function Buffer(fd, bufsize, sendAjax, recvAjax) {
	var rd = 0, wr = 0, rw = 0, size = 0;
	var buffer = new Array();
	var that = this;

	function getpacket() {
		if (rw != rd && size-- > 0) {
			assert(rw != wr);
			var result = buffer[rw++];
			rw %= buffer.length;
			return result;
		} else {
			if (size < 0)
				size = 0;
			return null;
		}
	}

	function get() {
		if (rw == rd && size-- > 0) {
			var result = buffer[rw++];
			assert(result.continuation == null);
			rw %= buffer.length;
			rd = rw;
			return result.message;
		} else {
			if (size < 0)
				size = 0;
			return null;
		}
	}

	function getresult() {
		var msg;
		var result = new Array();
		while ((msg = get()) != null) {
			result.push(msg);
		}
		return result;
	}

	function http_init() {
		if (window.XMLHttpRequest) {
			// code for IE7+, Firefox, Chrome, Opera, Safari
			return new XMLHttpRequest();
		} else {
			// code for IE6, IE5
			return new ActiveXObject("Microsoft.XMLHTTP");
		}
	}

	function http_read() {
		log.info("Buffer.http_read();");
		assert(recvAjax);
		assert(rw != rd);
		recvAjax = false;
		var xmlhttp = http_init();
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				log.info("xmlhttp.onreadystatechange();");
				result = xmlhttp.responseText.split("\n");
				result.shift();
				for (i = 0; i < result.length; ++i) {
					that.write(result[i]);
				}
				recvAjax = true;
				that.flush();
			}
		};
		xmlhttp.open("POST", "/cgi-bin/gateway.setl", true);
		xmlhttp.send(fd + "\n");
	}

	function http_write() {
		assert(rw == rd);
		var result = getresult();
		assert(rw == rd);
		assert(rw == wr);
		if (result.length > 0) {
			var xmlhttp = http_init();
			xmlhttp.open("POST", "/cgi-bin/flush.setl", true);
			xmlhttp.send(fd + "\n" + result.join("\n"));
		}
	}

	function forward() {
		log.debug("Buffer.forward(): fd=" + fd + "; rw=" + rw + "; rd=" + rd
				+ "; wr=" + wr + "; size=" + size + ";");
		var packet;
		while ((packet = getpacket()) != null) {
			packet.continuation(packet.message);
			packet.continuation = null;
			packet.message = null;
		}
		if (size > 0) {
			assert(rw == rd);
			if (sendAjax) {
				http_write();
			}
		} else {
			if (rw != rd && recvAjax) {
				http_read();
			}
		}
	}

	this.write = function(msg) {
		assert(msg != null);
		buffer[wr++].message = msg;
		wr %= buffer.length;
		if (wr == rw) {
			log.error("write buffer overflow");
		}
	};

	this.read = function(continuation) {
		assert(continuation != null);
		buffer[rd++].continuation = continuation;
		rd %= buffer.length;
		if (rd == rw) {
			log.error("read buffer overflow");
		}
		forward();
	};

	this.flush = function() {
		size = (wr - rw + buffer.length) % buffer.length;
		forward();
	};

	for ( var i = 0; i < bufsize; ++i) {
		buffer.push(new Packet());
	}
}

function Handles() {
	var size = 0;
	var index = new Array();

	this.indexOf = function(handle) {
		return index[handle];
	};

	this.add = function(handle) {
		index[handle] = handle.length;
		handle.push(handle);
	};

	this.remove = function(handle) {
		var i = index[handle];
		if (i != null) {
			index[handle] = null;
			assert(handle[i] == handle);
			if (i < --size) {
				handle[i] = handle[size];
			} else {
				assert(i == size);
			}
			handle[size] = null;
		}
	};
}

function timeout(fd) {
	function recv() {
		getline(fd[0], function(msg) {
			setTimeout(msg, function() {
				putline(fd[1], msg);
				recv();
			});
		});
	}

	recv();
}

function Composix(ctrlfd) {
	var buffer = new Buffer(ctrlfd, 64, false, false);
	var inputbuffer = new Buffer(1, 64, false, true);
	var xtrabuffer = new Buffer(5, 64, false, true);
	var fwdbuffer = new Buffer(1, 64, false, false);
	var outputbuffer = new Buffer(1, 64, true, false);
	var thebuffer = new Buffer(4, 64, true, false);

	var iBuffer = [ inputbuffer, fwdbuffer, null, null, null, xtrabuffer ];
	var oBuffer = [ fwdbuffer, outputbuffer, null, null, thebuffer ];

	var commands = [];

	function iBufferOf(fd) {
		switch (fd) {
		case ctrlfd:
			return buffer;
		case 0:
			return inputbuffer;
		case 1:
			return fwdbuffer;
		case 5:
			return xtrabuffer;
		default:
			return iBuffer[fd];
		}
	}

	function oBufferOf(fd) {
		switch (fd) {
		case ctrlfd:
			return buffer;
		case 0:
			return fwdbuffer;
		case 1:
			return outputbuffer;
		case 4:
			return thebuffer;
		default:
			return oBuffer[fd];
		}
	}

	function recv(msg) {
		log.info("Composix.recv();");
		log.info(msg);
		var head = document.getElementsByTagName("head")[0];
		var script = document.createElement("script");
		script.type = msg.type;
		script.src = msg.src;
		head.appendChild(script);
	}

	function iterate(msg) {
		if (msg != null) {
			recv(msg);
		}
		iBufferOf(ctrlfd).read(iterate);
	}

	function run() {
		iterate(null);
	}

	this.getline = function(fd, continuation) {
		log.info("getline(fd,continuation);");
		log.info(fd);
		log.debug(continuation);
		iBufferOf(fd).read(continuation);
	};

	this.putline = function(fd, line) {
		log.info("putline(fd,line);");
		log.info(fd);
		log.info(line);
		oBufferOf(fd).write(line);
	};

	this.flush = function(fd) {
		log.info("flush(fd);");
		log.info(fd);
		oBufferOf(fd).flush();
	};

	this.pipe = function pipe(pipefd) {
		assert(pipefd instanceof Array);
		if (pipefd.length > 2) {
			pipefd.length = 2;
		}
		var buf = new Buffer(null, 16, false, false);
		if (typeof (pipefd[0]) == "number" && pipefd[0] >= 0
				&& (iBuffer[pipefd[0]] || iBuffer[pipefd[0]] == null)) {
			iBuffer[pipefd[0]] = buf;
		} else {
			pipefd[0] = iBuffer.length;
			iBuffer.push(buf);
		}
		if (typeof (pipefd[1]) == "number" && pipefd[1] >= 0
				&& (oBuffer[pipefd[1]] || oBuffer[pipefd[1]] == null)) {
			oBuffer[pipefd[1]] = buf;
		} else {
			pipefd[1] = oBuffer.length;
			oBuffer.push(buf);
		}
	};

	this.exec = function(command) {
		var stdin = [], stdout = [], stderr = [];
		this.pipe(stdin);
		this.pipe(stdout);
		this.pipe(stderr);
		var fd = [ stdin[0], stdout[1], stderr[1] ];
		commands.push(new command(fd));
		return [ stdin[1], stdout[0], stderr[0] ];
	};

	run();
}

jQuery(document).ready(function() {
	cpx = new Composix(3);
//	var windowManager = new JSWM();
//	jQuery('#CPX').click(function() {
//		windowManager.openNew(500, 300, 'right', 'center', {
//			title : 'JS filled window'
//		});
//	});
	main();
});
